package rwth.rz.matse.sound;
import java.io.File;
import java.io.IOException;

import javax.sound.sampled.*;


public class WaveSound implements Runnable
{
	private static final int	EXTERNAL_BUFFER_SIZE = 128000;
	private File soundFile;

	AudioInputStream audioInputStream;
	AudioFormat	audioFormat;
	SourceDataLine	line;
	
	public WaveSound(File f) throws UnsupportedAudioFileException, IOException, LineUnavailableException{
		soundFile = f;
		initFile();
	}
	
	public WaveSound(String filename) throws UnsupportedAudioFileException, IOException, LineUnavailableException{
		soundFile = new File(filename);
		initFile();
	}
	
	public void play() throws UnsupportedAudioFileException, IOException, LineUnavailableException{
		playfile();
	}

	@Override
	public void run() {
		try{
			playfile();
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	private void initFile() throws UnsupportedAudioFileException, IOException, LineUnavailableException{
		audioInputStream = AudioSystem.getAudioInputStream(soundFile);
		/*
		From the AudioInputStream, i.e. from the sound file,
		we fetch information about the format of the
		audio data.
		These information include the sampling frequency,
		the number of
		channels and the size of the samples.
		These information
		are needed to ask Java Sound for a suitable output line
		for this audio file.
		*/
		audioFormat = audioInputStream.getFormat();
		
		/*
		Asking for a line is a rather tricky thing.
		We have to construct an Info object that specifies
		the desired properties for the line.
		First, we have to say which kind of line we want. The
		possibilities are: SourceDataLine (for playback), Clip
		(for repeated playback)	and TargetDataLine (for
		recording).
		Here, we want to do normal playback, so we ask for
		a SourceDataLine.
		Then, we have to pass an AudioFormat object, so that
		the Line knows which format the data passed to it
		will have.
		Furthermore, we can give Java Sound a hint about how
		big the internal buffer for the line should be. This
		isn't used here, signaling that we
		don't care about the exact size. Java Sound will use
		some default value for the buffer size.
		*/
		line = null;
		DataLine.Info	info = new DataLine.Info(SourceDataLine.class, audioFormat);
		
		line = (SourceDataLine) AudioSystem.getLine(info);
		
		/*
		The line is there, but it is not yet ready to
		receive audio data. We have to open the line.
		*/
		line.open(audioFormat);
	
	}
	
	private void playfile() throws UnsupportedAudioFileException, IOException, LineUnavailableException{

		/*
		Still not enough. The line now can receive data,
		but will not pass them on to the audio output device
		(which means to your sound card). This has to be
		activated.
		*/
		line.start();
		
		/*
		Ok, finally the line is prepared. Now comes the real
		job: we have to write data to the line. We do this
		in a loop. First, we read data from the
		AudioInputStream to a buffer. Then, we write from
		this buffer to the Line. This is done until the end
		of the file is reached, which is detected by a
		return value of -1 from the read method of the
		AudioInputStream.
		*/
		int	nBytesRead = 0;
		byte[]	abData = new byte[EXTERNAL_BUFFER_SIZE];
		while (nBytesRead != -1){
			try{
			nBytesRead = audioInputStream.read(abData, 0, abData.length);
			}catch (IOException e){
				e.printStackTrace();
			}
			if (nBytesRead >= 0){
				//int	nBytesWritten = 
				line.write(abData, 0, nBytesRead);
			}
		}
		
		/*
		Wait until all data are played.
		This is only necessary because of the bug noted below.
		(If we do not wait, we would interrupt the playback by
		prematurely closing the line and exiting the VM.)
		
		Thanks to Margie Fitch for bringing me on the right
		path to this solution.
		*/
		line.drain();
		
		/*
		All data are played. We can close the shop.
		*/
		line.close();
	}
	
	public void setVolume(double vol){
		if(vol > 1d) vol = 1d;
		else if(vol < 0d) vol = 0d;
		
	    FloatControl gainControl = (FloatControl)line.getControl(FloatControl.Type.MASTER_GAIN);
	    double gain = vol;    // number between 0 and 1 (loudest)
	    float dB = (float)(Math.log(gain)/Math.log(10.0)*20.0);
	    gainControl.setValue(dB);
	}
	
	public void setPan(double bal){
		if(bal > 1d) bal = 1d;
		else if(bal < -1d) bal = -1d;
		try{
			FloatControl balanceControl = (FloatControl)line.getControl(FloatControl.Type.PAN);
			balanceControl.setValue((float) bal);
		}catch(Exception e){	}
	}
	
	public void setBalance(double bal){
		if(bal > 1d) bal = 1d;
		else if(bal < -1d) bal = -1d;
		try{
			FloatControl balanceControl = (FloatControl)line.getControl(FloatControl.Type.BALANCE);
			balanceControl.setValue((float) bal);
		}catch(Exception e){	}
	}
}